﻿using System;
using System.Collections.Generic;
using Adaptive.Agrona.Concurrent;
using Microsoft.Extensions.Logging;

using Unity.Commons.Client;
using Unity.Commons.Client.Buffer;
using Unity.Commons.Client.Configuration;
using Unity.Commons.Client.Exceptions;
using Unity.Commons.Client.Offline;
using Unity.Connections;
using Unity.Credentials;
using Unity.Logger;

namespace Unity.Solace.Client
{
    public static class UnityClientFactory
    {
        private static readonly ILogger Logger = UnityLogger.ConsoleLoggerFactory.CreateLogger(nameof(UnityClientFactory));

        static IUnityProducer GetUnityStreamProducer()
        {
            return null;
        }

        public static IUnityClient MakeUnityClient(string csiId, IUnityCredentials credentials, IOfflineHandler offlineHandler)
        {
            return MakeUnityClient(csiId, credentials, null, offlineHandler, GetUnityStreamProducer(), GetUnityStreamProducer());
        }

        public static IUnityClient MakeUnityClient(string csiId, IUnityCredentials credentials, string bufferFilepath)
        {
            return MakeUnityClient(csiId, credentials, bufferFilepath, null, GetUnityStreamProducer(), GetUnityStreamProducer());
        }

        static IUnityClient MakeUnityClient(string csiId, IUnityCredentials credentials, string bufferFilepath, IOfflineHandler offlineHandler,
            IUnityProducer producer, IUnityProducer resourceProducer)
        {
            IUnityConfiguration configuration = new PropertyConfiguration();
            IAtomicBuffer mainByteBuffer;
            UnityBufferFile offlineBufferFile = null;

            if(!string.IsNullOrWhiteSpace(bufferFilepath))
            {
                try
                {
                    offlineBufferFile = UnityBufferFile.MakeNewBufferFile(bufferFilepath, configuration.GetMainBufferSize());
                }
                catch(Exception exception)
                {
                    throw new UnityBufferFileException($"Error with buffer file {bufferFilepath}", exception);
                }
            }

            if (offlineBufferFile != null)
            {
                mainByteBuffer = offlineBufferFile.TryMakeBufferFile();
            }
            else
            {
                mainByteBuffer = new UnsafeBuffer(new byte[configuration.GetMainBufferSize()]);
            }

            IUnityBuffer offlineBuffer;
            if (offlineHandler != null)
            {
                IAtomicBuffer offlineByteBuffer = new UnsafeBuffer(new byte[configuration.GetOfflineBufferSize()]);
                offlineBuffer = new UnityRingBuffer(new OfflineProducer(offlineHandler), null, offlineByteBuffer);
            }
            else
            {
                offlineBuffer = null;
            }
            IUnityBuffer mainBuffer = new UnityRingBuffer(producer, offlineBuffer, mainByteBuffer);
            if(offlineBufferFile != null)
            {
                var recovery = offlineBufferFile.Recovery;
                if (recovery != null && recovery.Count > 0)
                {
                    ReplayContent(mainBuffer, recovery);
                }
            }

            IUnityClient client = new UnityClient(csiId, credentials, mainBuffer, offlineBuffer, offlineBufferFile, resourceProducer);
            Logger.LogInformation("Buffer capacity is {} messages", client.GetStatistics().GetBufferCapacity());
            return client;
        }

        private static void ReplayContent(IUnityBuffer buffer, ICollection<byte[]> content)
        {
            foreach(byte[] message in content) 
            {
                int claim = buffer.Claim(message.Length);
                if(claim > 0)
                {
                    try
                    {
                        buffer.GetBuffer().PutBytes(claim, message);
                        buffer.Commit(claim);
                    }
                    catch(Exception exception)
                    {
                        Logger.LogError(exception, "Error commit replay message");
                        buffer.Abort(claim);
                    }
                }
            }
        }
    }
}